package chen.huai.jie.spring.xmlconfig.mybatis;

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.IncompleteElementException;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.annotation.ProviderSqlSource;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

public class AutoMapperAnnotationBuilder {
    private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet();
    private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet();
    private final Map<SqlCommandType, SqlBuilder> commandBuilders = new HashMap();
    private Configuration configuration;
    private MapperBuilderAssistant assistant;
    private Class<?> type;
    private EntityPortray entityPortray;

    public AutoMapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.assistant = new MapperBuilderAssistant(configuration, resource);
        this.configuration = configuration;
        this.type = type;
        this.sqlAnnotationTypes.add(Select.class);
        this.sqlAnnotationTypes.add(Insert.class);
        this.sqlAnnotationTypes.add(Update.class);
        this.sqlAnnotationTypes.add(Delete.class);
        this.sqlProviderAnnotationTypes.add(SelectProvider.class);
        this.sqlProviderAnnotationTypes.add(InsertProvider.class);
        this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
        this.sqlProviderAnnotationTypes.add(DeleteProvider.class);
        this.commandBuilders.put(SqlCommandType.SELECT, new SelectBuilder());
        this.commandBuilders.put(SqlCommandType.INSERT, new InsertBuilder());
        this.commandBuilders.put(SqlCommandType.UPDATE, new UpdateBuilder());
        this.commandBuilders.put(SqlCommandType.DELETE, new DeleteBuilder());
    }

    public void parse() {
        String resource = this.type.toString();
        if (!this.configuration.isResourceLoaded(resource)) {
            this.entityPortray = MapperUtils.getEntityPortray(this.type, this.type);
            this.loadXmlResource();
            this.configuration.addLoadedResource(resource);
            this.assistant.setCurrentNamespace(this.type.getName());
            this.parseCache();
            this.parseCacheRef();
            Method[] methods = this.type.getMethods();
            Method[] var3 = methods;
            int var4 = methods.length;

            for (int var5 = 0; var5 < var4; ++var5) {
                Method method = var3[var5];

                try {
                    if (!method.isBridge()) {
                        this.parseStatement(method);
                    }
                } catch (IncompleteElementException var8) {
                    ;
                }
            }

            MapperUtils.destroyPortray(this.type);
        }

    }

    private void parseStatement(Method method) {
        Class<?> parameterTypeClass = MapperUtils.getParameterType(method);
        LanguageDriver languageDriver = this.getLanguageDriver(method);
        SqlSource sqlSource = this.getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
        if (sqlSource != null) {
            Options options = (Options) method.getAnnotation(Options.class);
            String mappedStatementId = this.type.getName() + "." + method.getName();
            Integer fetchSize = null;
            Integer timeout = null;
            StatementType statementType = StatementType.PREPARED;
            ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
            SqlCommandType sqlCommandType = this.getSqlCommandType(method);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = !isSelect;
            boolean useCache = isSelect;
            KeyBuilder keyBuilder = new KeyBuilder(this.configuration, this.assistant);
            keyBuilder.buildKey(sqlCommandType, method, mappedStatementId, this.entityPortray, options, languageDriver);
            if (options != null) {
                if (Options.FlushCachePolicy.TRUE.equals(options.flushCache())) {
                    flushCache = true;
                } else if (Options.FlushCachePolicy.FALSE.equals(options.flushCache())) {
                    flushCache = false;
                }

                useCache = options.useCache();
                fetchSize = options.fetchSize() <= -1 && options.fetchSize() != -2147483648 ? null : options.fetchSize();
                timeout = options.timeout() > -1 ? options.timeout() : null;
                statementType = options.statementType();
                resultSetType = options.resultSetType();
            }

            String resultMapId = (new ResultBuilder()).buildResultMap(this.configuration, this.assistant, this.type, method, isSelect);
            this.assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, (String) null, parameterTypeClass, resultMapId, MapperUtils.getReturnType(method, this.type), resultSetType, flushCache, useCache, false, keyBuilder.getKeyGenerator(), keyBuilder.getKeyProperty(), keyBuilder.getKeyColumn(), (String) null, languageDriver, options != null ? this.nullOrEmpty(options.resultSets()) : null);
        }

    }

    private SqlCommandType getSqlCommandType(Method method) {
        Class<? extends Annotation> type = this.getSqlAnnotationType(method);
        if (type == null) {
            type = this.getSqlProviderAnnotationType(method);
            if (type == null) {
                String name = method.getName();
                if (!name.startsWith("select") && !name.startsWith("find")) {
                    if (!name.startsWith("update") && !name.startsWith("invalid")) {
                        if (name.startsWith("delete")) {
                            type = Delete.class;
                        } else {
                            if (!name.startsWith("insert")) {
                                return SqlCommandType.UNKNOWN;
                            }

                            type = Insert.class;
                        }
                    } else {
                        type = Update.class;
                    }
                } else {
                    type = Select.class;
                }
            } else if (type == SelectProvider.class) {
                type = Select.class;
            } else if (type == InsertProvider.class) {
                type = Insert.class;
            } else if (type == UpdateProvider.class) {
                type = Update.class;
            } else if (type == DeleteProvider.class) {
                type = Delete.class;
            }
        }

        return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
    }

    private Class<? extends Annotation> getSqlAnnotationType(Method method) {
        return this.chooseAnnotationType(method, this.sqlAnnotationTypes);
    }

    private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
        return this.chooseAnnotationType(method, this.sqlProviderAnnotationTypes);
    }

    private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
        Iterator var3 = types.iterator();

        Class type;
        Annotation annotation;
        do {
            if (!var3.hasNext()) {
                return null;
            }

            type = (Class) var3.next();
            annotation = method.getAnnotation(type);
        } while (annotation == null);

        return type;
    }

    private LanguageDriver getLanguageDriver(Method method) {
        Lang lang = (Lang) method.getAnnotation(Lang.class);
        Class<?> langClass = null;
        if (lang != null) {
            langClass = lang.value();
        }

        return this.assistant.getLanguageDriver(langClass);
    }

    private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
        try {
            Class<? extends Annotation> sqlAnnotationType = this.getSqlAnnotationType(method);
            Class<? extends Annotation> sqlProviderAnnotationType = this.getSqlProviderAnnotationType(method);
            Annotation sqlProviderAnnotation;
            if (sqlAnnotationType != null) {
                if (sqlProviderAnnotationType != null) {
                    throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
                } else {
                    sqlProviderAnnotation = method.getAnnotation(sqlAnnotationType);
                    String[] strings = (String[]) ((String[]) sqlProviderAnnotation.getClass().getMethod("value").invoke(sqlProviderAnnotation));
                    return MapperUtils.buildSqlSourceFromStrings(this.configuration, strings, parameterType, languageDriver);
                }
            } else if (sqlProviderAnnotationType != null) {
                sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
                return new ProviderSqlSource(this.assistant.getConfiguration(), sqlProviderAnnotation);
            } else {
                return MapperUtils.buildSqlSourceFromStrings(this.configuration, new String[]{this.buildSqlFromMethodName(method)}, parameterType, languageDriver);
            }
        } catch (Exception var8) {
            throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + var8, var8);
        }
    }

    private String buildSqlFromMethodName(Method method) {
        SqlCommandType commandType = this.getSqlCommandType(method);
        SqlBuilder commandBuilder = (SqlBuilder) this.commandBuilders.get(commandType);
        return commandBuilder != null ? commandBuilder.buildSql(this.type, method, this.entityPortray) : null;
    }

    private void loadXmlResource() {
        if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
            String xmlResource = this.type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = null;

            try {
                inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
            } catch (IOException var4) {
                ;
            }

            if (inputStream != null) {
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                xmlParser.parse();
            }
        }

    }

    private void parseCache() {
        CacheNamespace cacheDomain = (CacheNamespace) this.type.getAnnotation(CacheNamespace.class);
        if (cacheDomain != null) {
            Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
            Long flushInterval = cacheDomain.flushInterval() == 0L ? null : cacheDomain.flushInterval();
            this.assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), (Properties) null);
        }

    }

    private void parseCacheRef() {
        CacheNamespaceRef cacheDomainRef = (CacheNamespaceRef) this.type.getAnnotation(CacheNamespaceRef.class);
        if (cacheDomainRef != null) {
            this.assistant.useCacheRef(cacheDomainRef.value().getName());
        }

    }

    private String nullOrEmpty(String value) {
        return value != null && value.trim().length() != 0 ? value : null;
    }
}
